﻿#region Apache License Version 2.0
/*----------------------------------------------------------------

Copyright 2025 Jeffrey Su & Suzhou Senparc Network Technology Co.,Ltd.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the specific language governing permissions
and limitations under the License.

Detail: https://github.com/JeffreySu/WeiXinMPSDK/blob/master/license.md

----------------------------------------------------------------*/
#endregion Apache License Version 2.0

/*----------------------------------------------------------------
    Copyright (C) 2025 Senparc
  
    文件名：TenPayHttpClient.cs
    文件功能描述：TenPayHttpClient
    
    
    创建标识：Senparc - 20210920

    修改标识：Senparc - 20220511
    修改描述：v0.6.2.2 TenPayHttpClient 赋值问题

    
----------------------------------------------------------------*/

using Client.TenPayHttpClient.Signer;
using Senparc.CO2NET;
using Senparc.CO2NET.Extensions;
using Senparc.CO2NET.Helpers;
using Senparc.CO2NET.HttpUtility;
using Senparc.CO2NET.Trace;
using Senparc.Weixin;
using Senparc.Weixin.Entities;
using Senparc.Weixin.TenPayV3;
using Senparc.Weixin.TenPayV3.Apis.BasePay;
using Senparc.Weixin.TenPayV3.Apis.Entities;
using Senparc.Weixin.TenPayV3.TenPayHttpClient.Verifier;
using System;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace Senparc.Weixin.TenPayV3.TenPayHttpClient
{

    public class BasePayApis2
    {
        private readonly SenparcHttpClient _httpClient;
        private ISenparcWeixinSettingForTenpayV3 _tenpayV3Setting;
        //private CertType CertType => _tenpayV3Setting.EncryptionType == "SM" ? CertType.SM : CertType.RSA;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="senparcWeixinSettingForTenpayV3"></param>
        public BasePayApis2(SenparcHttpClient httpClient, ISenparcWeixinSettingForTenpayV3 senparcWeixinSettingForTenpayV3 = null/*, CertType certType = CertType.RSA*/)
        {
            this._httpClient = httpClient;
            _tenpayV3Setting = senparcWeixinSettingForTenpayV3 ?? Senparc.Weixin.Config.SenparcWeixinSetting.TenpayV3Setting;
            //this._certType = certType;
        }

        /// <summary>
        /// 返回可用的微信支付地址（自动判断是否使用沙箱）
        /// </summary>
        /// <param name="urlFormat">如：<code>https://api.mch.weixin.qq.com/{0}pay/unifiedorder</code></param>
        /// <returns></returns>
        private static string ReturnPayApiUrl(string urlFormat)
        {
            //注意：目前微信支付 V3 还没有支持沙箱，此处只是预留
            return string.Format(urlFormat, Senparc.Weixin.Config.UseSandBoxPay ? "sandboxnew/" : "");
        }

        /// <summary>
        /// JSAPI下单接口
        /// <para>在微信支付服务后台生成JSAPI预支付交易单，返回预支付交易会话标识</para>
        /// </summary>
        /// <param name="data">微信支付需要POST的Data数据</param>
        /// <param name="timeOut">超时时间，单位为ms </param>
        /// <returns></returns>
        public async Task<JsApiReturnJson> JsApiAsync(TransactionsRequestData data, int timeOut = Config.TIME_OUT)
        {
            var url = ReturnPayApiUrl(Senparc.Weixin.Config.TenPayV3Host + "/{0}v3/pay/transactions/jsapi");

            //HttpClient hc = null;//注入
            TenPayHttpClient tenPayApiRequest = new(_httpClient, _tenpayV3Setting);
            return await tenPayApiRequest.SendAsync<JsApiReturnJson>(url, data, timeOut);
        }
    }

    public class TenPayHttpClient
    {
        private readonly SenparcHttpClient _httpClient;
        private ISenparcWeixinSettingForTenpayV3 _tenpayV3Setting;
        private readonly HttpClient _client;
        private readonly ISigner _signer;
        private readonly IVerifier _verifier;
        private CertType CertType => _tenpayV3Setting.EncryptionType.Value;

        public TenPayHttpClient(SenparcHttpClient httpClient, ISenparcWeixinSettingForTenpayV3 senparcWeixinSettingForTenpayV3 = null)
        {
            this._httpClient = httpClient;
            this._client = this._httpClient.Client;
            _tenpayV3Setting = senparcWeixinSettingForTenpayV3 ?? Senparc.Weixin.Config.SenparcWeixinSetting.TenpayV3Setting;

            if (!_tenpayV3Setting.EncryptionType.HasValue)
            {
                throw new Senparc.Weixin.Exceptions.WeixinException("没有设置证书加密类型（EncryptionType）");
            }

            //从工厂获得签名和验签的方法类
            _signer = TenPayCertFactory.GetSigner(CertType);
            _verifier = TenPayCertFactory.GetVerifier(CertType);

            #region 配置UA

            //ACCEPT header
            _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));

            //User-Agent header
            var userAgentValues = UserAgentValues.Instance;
            _client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Senparc.Weixin.TenPayV3-C#", userAgentValues.TenPayV3Version));
            _client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue($"(Senparc.Weixin {userAgentValues.SenparcWeixinVersion})"));
            _client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(".NET", userAgentValues.RuntimeVersion));
            _client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue($"({userAgentValues.OSVersion})"));

            #endregion
        }

        public async Task<T> SendAsync<T>(string url, object data, int timeOut = Senparc.Weixin.Config.TIME_OUT, ApiRequestMethod requestMethod = ApiRequestMethod.POST, bool checkSign = true, Func<T> createDefaultInstance = null)
                   where T : ReturnJsonBase/*, new()*/
        {
            T result = null;
            HttpMethod method = GetHttpMethod(requestMethod);

            try
            {
                var request = new HttpRequestMessage(method, url);

                //设置超时时间
                _client.Timeout = TimeSpan.FromMilliseconds(timeOut);

                //设置请求 Json 字符串
                string jsonString = data != null
                    ? data.ToJson(false, new Newtonsoft.Json.JsonSerializerSettings() { NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore })
                    : "";
                WeixinTrace.SendApiPostDataLog(url, jsonString); //记录Post的Json数据
                request.Content = new StringContent(jsonString, Encoding.UTF8, mediaType: "application/json");

                // 进行签名
                var authorization = await GenerateAuthorizationHeader(request);
                request.Headers.Add("Authorization", $"WECHATPAY2-{_signer.GetAlgorithm()} {authorization}");

                // 发送请求
                var responseMessage = await _client.SendAsync(request);

                //获取响应结果
                string content = await responseMessage.Content.ReadAsStringAsync();//TODO:如果不正确也要返回详情
#if DEBUG
                Console.WriteLine("Content:" + content + ",,Headers:" + responseMessage.Headers.ToString());
#endif

                //检查响应代码
                TenPayApiResultCode resutlCode = TenPayApiResultCode.TryGetCode(responseMessage.StatusCode, content);

                if (resutlCode.Success)
                {
                    result = content.GetObject<T>();

                    if (checkSign)
                    {
                        result.VerifySignSuccess = await VerifyResponseMessage(responseMessage, content);
                    }
                }
                else
                {
                    result = createDefaultInstance?.Invoke() ?? GetInstance<T>(true);
                    resutlCode.Additional = content;
                }
                result.ResultCode = resutlCode;

                return result;
            }
            catch (Exception ex)
            {
                SenparcTrace.BaseExceptionLog(ex);
                result = createDefaultInstance?.Invoke() ?? GetInstance<T>(false);
                if (result != null)
                {
                    result.ResultCode = new() { ErrorMessage = ex.Message };
                }

                return result;
            }
        }

        protected HttpMethod GetHttpMethod(ApiRequestMethod requestMethod)
        {
            return requestMethod switch
            {
                ApiRequestMethod.GET => HttpMethod.Get,
                ApiRequestMethod.POST => HttpMethod.Post,
                ApiRequestMethod.PUT => HttpMethod.Put,
                ApiRequestMethod.PATCH => HttpMethod.Patch,
                ApiRequestMethod.DELETE => HttpMethod.Delete,
                _ => throw new ArgumentOutOfRangeException(nameof(requestMethod)),
            };
        }

        protected async Task<string> GenerateAuthorizationHeader(HttpRequestMessage request)
        {
            string method = request.Method.ToString();
            string body = "";
            if (method == "POST" || method == "PUT" || method == "PATCH")
            {
                var content = request.Content;
                body = await content.ReadAsStringAsync();
            }

            string uri = request.RequestUri.PathAndQuery;
            var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
            string nonce = Path.GetRandomFileName();

            string message = $"{method}\n{uri}\n{timestamp}\n{nonce}\n{body}\n";

            string signature = _signer.Sign(message, _tenpayV3Setting.TenPayV3_PrivateKey);

            return $"mchid=\"{_tenpayV3Setting.TenPayV3_MchId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{_tenpayV3Setting.TenPayV3_SerialNumber}\",signature=\"{signature}\"";
        }

        protected async Task<bool> VerifyResponseMessage(HttpResponseMessage responseMessage, string content)
        {
            var wechatpayTimestamp = responseMessage.Headers.GetValues("Wechatpay-Timestamp").First();
            var wechatpayNonce = responseMessage.Headers.GetValues("Wechatpay-Nonce").First();
            var wechatpaySignatureBase64 = responseMessage.Headers.GetValues("Wechatpay-Signature").First();//后续需要base64解码
            var wechatpaySerial = responseMessage.Headers.GetValues("Wechatpay-Serial").First();

            try
            {
                var pubKey = await TenPayV3InfoCollection.GetAPIv3PublicKeyAsync(this._tenpayV3Setting, wechatpaySerial);
                return _verifier.Verify(wechatpayTimestamp, wechatpayNonce, wechatpaySignatureBase64, content, pubKey, this._tenpayV3Setting.TenPayV3_TenPayPubKeyEnable);
            }
            catch (Exception ex)
            {
                throw new TenpayApiRequestException("RequestAsync 签名验证失败：" + ex.Message, ex);
            }
        }

        /// <summary>
        /// 获取实例
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="throwIfFaild"></param>
        /// <returns></returns>
        private T GetInstance<T>(bool throwIfFaild)
            where T : ReturnJsonBase
        {
            if (typeof(T).IsClass)
            {
                return Senparc.CO2NET.Helpers.ReflectionHelper.CreateInstance<T>(typeof(T).FullName, typeof(T).Assembly.GetName().Name);
            }
            else if (throwIfFaild)
            {
                throw new TenpayApiRequestException("GetInstance 失败，此类型无法自动生成：" + typeof(T).FullName);
            }
            return null;
        }
    }
}
